package com.itcast.rediscache;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.itcast.rediscache.pubsub.sub.MessageChatReceiver;
import com.itcast.rediscache.pubsub.sub.MessagePlayReceiver;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

/**
 * 配置类:
 * Jedis 连接池信息配置
 * Created by zhangtian on 2017/4/25.
 */
@Configuration
@EnableCaching
public class RedisConfig implements CachingConfigurer {
    @Value("${spring.data.redis.host}")
    private String host ;
    @Value("${spring.data.redis.port}")
    private Integer port ;
    /**
     * 密码暂时不用
     */
    @Value("${spring.data.redis.password}")
    private String password ;
    @Value("${spring.data.redis.timeout}")
    private Integer timeout ;
    @Value("${spring.data.redis.database}")
    private Integer database ;

    // ------------------------ lettuce 连接池配置 ------------------------
    @Value("${spring.data.redis.lettuce.pool.max-idle}")
    private Integer maxLIdleConnections ;
    @Value("${spring.data.redis.lettuce.pool.min-idle}")
    private Integer minLIdleConnections ;
    @Value("${spring.data.redis.lettuce.pool.max-active}")
    private Integer maxLConnections ;
    @Value("${spring.data.redis.lettuce.pool.max-wait}")
    private Long maxLWaitTime ;
    @Value("${spring.data.redis.lettuce.pool.time-between-eviction-runs}")
    private Long timeLBetweenEvictionRunsMillis ;

    /**
     * 自定义缓存key的生成策略。
     * 默认的生成策略是看不懂的(乱码内容) 通过Spring 的依赖注入特性进行自定义的配置注入并且此类是一个配置类可以更多程度的自定义配置
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }

    // ******************************** 此处为lettuce的连接池配置 ********************************
    /**
     * 这里lettuce使用的连接池
     * @return
     */
    @Bean(value = "genericObjectPoolConfig")
    public GenericObjectPoolConfig<Object> initLettucePoolConfig() {
        // 连接池配置
        GenericObjectPoolConfig<Object> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxIdle(maxLIdleConnections);
        poolConfig.setMinIdle(minLIdleConnections);
        poolConfig.setMaxTotal(maxLConnections);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setTestOnReturn(true);
        poolConfig.setTestWhileIdle(true);
        poolConfig.setNumTestsPerEvictionRun(10);
        poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(timeLBetweenEvictionRunsMillis * 60 * 1000));
        poolConfig.setMaxWaitMillis(maxLWaitTime);

        return poolConfig ;
    }

    // ******************************** 此处为lettuce的连接工厂配置 ********************************
    /**
     * 这里面是lettuce的连接工厂，一次只有有一样，使用@Primary处理
     * @param genericObjectPoolConfig
     * @return
     */
    @Bean(value = "lettuceRedisConnectionFactory")
    public RedisConnectionFactory initLettuceRedisConnectionFactory(@Autowired @Qualifier(value = "genericObjectPoolConfig")
                                                                        GenericObjectPoolConfig genericObjectPoolConfig) {
        // 单机版Redis
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration() ;
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        //redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        redisStandaloneConfiguration.setDatabase(0);

        // 集群版本以及哨兵模式的单独查资料 ......

        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder
                = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(timeout));
        LettucePoolingClientConfiguration lettucePoolingClientConfiguration = builder.build();

        builder.poolConfig(genericObjectPoolConfig);

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, lettucePoolingClientConfiguration) ;
        return lettuceConnectionFactory ;
    }

    /**
     * 缓存配置管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 以锁写入的方式创建RedisCacheWriter对象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
        /*
            设置CacheManager的Value序列化方式为JdkSerializationRedisSerializer,
            但其实RedisCacheConfiguration默认就是使用
            StringRedisSerializer序列化key，
            JdkSerializationRedisSerializer序列化value,
            所以以下注释代码就是默认实现，没必要写，直接注释掉
         */
        // RedisSerializationContext.SerializationPair pair = RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
        // RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //创建默认缓存配置对象
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
        return cacheManager;
    }

    /**
     * jackson序列化配置
     * @return
     */
    @Bean
    public Jackson2JsonRedisSerializer<Object> serializer() {
        ObjectMapper objectMapper = new ObjectMapper();

        // 指定要序列化的域，field,get和set,以及修饰符范围，ANY是都有包括private和public
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型，类必须是非final修饰的，final修饰的类，比如String,Integer等会跑出异常
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值（默认使用jdk序列化）
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        return jackson2JsonRedisSerializer;
    }

    /**
     * 实例化 RedisTemplate 对象
     * @return
     */
    @Bean(value = "redisTemplate")
    public RedisTemplate<String, String> functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory,
                                                                     Jackson2JsonRedisSerializer jackson2JsonRedisSerializer) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>() ;
        initDomainRedisTemplate(redisTemplate, redisConnectionFactory, jackson2JsonRedisSerializer);
        return redisTemplate ;// StringRedisTemplate是RedisTemplate<String, String>的子类
    }

    /**
     * 设置数据存入 redis 的序列化方式
     * @param redisTemplate
     * @param factory
     * @param jackson2JsonRedisSerializer
     */
    private void initDomainRedisTemplate(RedisTemplate redisTemplate,
                                         RedisConnectionFactory factory,
                                         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer) {
        // 配置连接工厂
        redisTemplate.setConnectionFactory(factory);

        //定义key序列化方式
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long类型会出现异常信息;需要我们上面的自定义key生成策略，一般没必要
        // 使用StringRedisSerializer来序列化和反序列化redis的key
        redisTemplate.setKeySerializer(redisSerializer);
        // 设置hash key和value的序列化模式
        redisTemplate.setHashKeySerializer(redisSerializer);
        // 值采用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
    }

    /**
     * 实例化 ValueOperations 对象,可以使用 String 操作
     * @param redisTemplate
     * @return
     */
    @Bean(name = "jsonValueOperations")
    public <T> ValueOperations<String, T> valueOperations(RedisTemplate<String, T> redisTemplate) {
        return redisTemplate.opsForValue() ;
    }

    /**
     * 实例化 ListOperations 对象,可以使用 List 操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public <T> ListOperations<String, T> listOperations(RedisTemplate<String, T> redisTemplate) {
        return redisTemplate.opsForList() ;
    }

    /**
     * 实例化 SetOperations 对象,可以使用 Set 操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public <T> SetOperations<String, T> setOperations(RedisTemplate<String, T> redisTemplate) {
        return redisTemplate.opsForSet() ;
    }

    /**
     * 实例化 ZSetOperations 对象,可以使用 ZSet 操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public <T> ZSetOperations<String, T> zSetOperations(RedisTemplate<String, T> redisTemplate) {
        return redisTemplate.opsForZSet() ;
    }

    @Bean
    public <T> HashOperations<String, String, T> hashOperations(RedisTemplate<String, T> redisTemplate) {
        return redisTemplate.opsForHash() ;
    }

    // pipeLined等操作均在redisTemplate中封装

    /**
     * 这个和上面序列化是最关键的，后面的几个方法用不到可以去掉
     * @param redisConnectionFactory
     * @return
     */
    @Bean(value = "stringRedisTemplate")
    // @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    /**
     * 实例化 ValueOperations 对象,可以使用 String 操作
     * 这里不用上面的RedisTemplate  是因为上面的这个RedisTemplate的value序列化使用jackson序列化，会导致存入的数据无缘无故多出了双引号，如： "1234"，而不是1234
     * 这样会引起删除分布式锁的lua表达式无法删除数据，传的值必须是"\"1234\"",这样才能删除，故而使用StringRedisTemplate来进行数据存储
     * @param stringRedisTemplate
     * @return
     */
    @Bean(value = "stringValueOperations")
    public ValueOperations<String, String> valueStringOperations(StringRedisTemplate stringRedisTemplate) {
        return stringRedisTemplate.opsForValue() ;
    }

    /**
     * 功能描述: <br>
     * 〈实例化JedisCluster 集群分片版本〉
     * @Param: [jedisPoolConfig]
     * @Return: redis.clients.jedis.JedisCluster
     * @Author: zhangtian105
     * @Date: 2020/11/18 20:47
     */
    /*@Bean
    public JedisCluster initJedisCluster(JedisPoolConfig jedisPoolConfig) {
        Set<HostAndPort> nodeSet = new HashSet<>() ;
        nodeSet.add(new HostAndPort("localhost", 6380)) ;
        // ......... 集群分片模式就把多个host和ip配置进去

        JedisCluster jedisCluster = new JedisCluster(nodeSet, 2000, 3000, 1, password, jedisPoolConfig) ; // jedisPoolConfig根据集群配置定制化
        return jedisCluster ;
    }*/

    // 注册redis与订阅通道
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                   MessageListenerAdapter messageChatListenerAdapter,
                                                   MessageListenerAdapter messagePlayListenerAdapter,
                                                   Jackson2JsonRedisSerializer jackson2JsonRedisSerializer) {

        RedisMessageListenerContainer container = new RedisMessageListenerContainer() ;
        container.setConnectionFactory(connectionFactory);

        // 订阅一个叫chat的通道
        container.addMessageListener(messageChatListenerAdapter, new PatternTopic("chat"));
        // 订阅一个叫paly的通道
        container.addMessageListener(messagePlayListenerAdapter, new PatternTopic("play"));

        // 序列化对象（特别注意：发布的时候需要设置序列化；订阅方也需要设置序列化）
        container.setTopicSerializer(jackson2JsonRedisSerializer);
        return container ;
    }

    // 消息监听器适配器，绑定消息处理器，利用反射技术调用消息处理器的业务方法
    @Bean
    public MessageListenerAdapter messageChatListenerAdapter(MessageChatReceiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveChatMessage");
    }

    @Bean
    public MessageListenerAdapter messagePlayListenerAdapter(MessagePlayReceiver receiver) {
        return new MessageListenerAdapter(receiver, "receivePlayMessage");
    }
}
